1 /*
2 * Copyright (c) 2001, 2002 The XDoclet team
3 * All rights reserved.
4 */
5 package xdoclet;
6
7 import org.apache.commons.collections.CollectionUtils;
8 import org.apache.commons.collections.Predicate;
9 import org.apache.commons.logging.LogFactory;
10 import org.apache.commons.logging.LogConfigurationException;
11
12 import xdoclet.beans.BeanContextSupportEx;
13
14 import java.io.File;
15 import java.io.IOException;
16
17 import java.text.MessageFormat;
18
19 import java.util.Collection;
20 import java.util.Iterator;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.InvocationTargetException;
23
24 /***
25 * <p>A Plugin is responsible for the generation of a particular kind of file. It
26 * is also an abstraction of underlying generation mechanisms such as Velocity,
27 * Jelly and Betwixt.</p>
28 *
29 * <p>A plugin also implements {@link java.beans.beancontext.BeanContextChild},
30 * because it might be used in a Java Beans compliant environment such as
31 * an IDE. Therefore, this class is implicitly a {@link java.util.Collection},
32 * and all contained objects (package substitutions etc) are contained directly
33 * in the instances of this class. They are filtered out by various internal
34 * {@link Predicate}s.</p>
35 *
36 * <p>A plugin can operate in two different modes, depending on the
37 * value of the fileName:</p>
38 *
39 * <ul>
40 * <li>If there is a "{0}" in the fileName, one file will be generated for
41 * each object in the collection provided by the MetadataProvider.</li>
42 * <li>If there is no "{0}" in the fileName, one file will be generated for each object
43 * in the collection provided by the MetadataProvider. This makes it possible to use the XDoclet
44 * core for generation of files where the "metadata source" is other kinds of
45 * objects than e.g. xjavadoc.</li>
46 * </ul>
47 *
48 * @see PluginFactory
49 * @see XDoclet
50 *
51 * @author <a href="mailto:aslak.hellesoy at netcom.no">Aslak Hellesøy</a>
52 * @version $Revision: 1.29 $
53 */
54 public abstract class Plugin extends BeanContextSupportEx {
55 /*** Encoding of output. */
56 private String _encoding = "ISO-8859-1";
57 private String _packageName = "";
58 private String _fileName = "";
59
60 /*** The name. */
61 private String _name;
62
63 /*** The directory where plugin will write the files. */
64 private File _destinationDir;
65
66 private MetadataProvider _metadataProvider;
67
68 public Plugin() {
69 // We'll use our own name as default
70 setName(getClass().getName());
71 }
72
73 public XDoclet getXDoclet() {
74 return (XDoclet) getParent();
75 }
76
77 public void setMetadataProvider( MetadataProvider metadataProvider ) {
78 _metadataProvider = metadataProvider;
79 }
80
81 protected MetadataProvider getMetadataProvider() {
82 return _metadataProvider;
83 }
84
85 /***
86 * Gets the name of the plugin.
87 * @return the name of the plugin.
88 */
89 public final String getName() {
90 return _name;
91 }
92
93 /***
94 * Sets the name of the plugin.
95 * @param name the name of the plugin.
96 * @bean.property hidden="true"
97 */
98 public final void setName(String name) {
99 _name = name;
100 }
101
102 /***
103 * Gets the XML encoding.
104 *
105 * @see #setEncoding
106 * @return the XML encoding.
107 */
108 public String getEncoding() {
109 return _encoding;
110 }
111
112 /***
113 * Sets the encoding to use. Only applies to generation of XML files.
114 * Default is "ISO-8859-1"
115 *
116 * @bean.property
117 * shortDescription="Encoding of generated file(s). Applies to XML generation only."
118 * displayName="Encoding"
119 * @param encoding the encoding to use.
120 */
121 public void setEncoding(String encoding) {
122 _encoding = encoding;
123 }
124
125 /***
126 * Returns the destination directory (without package directory).
127 * @return the destination directory
128 */
129 public File getDestinationDir() {
130 return _destinationDir;
131 }
132
133 /***
134 * Sets (and creates) the destination directory.
135 *
136 * @bean.property
137 * shortDescription="Directory where files will be written."
138 * displayName="Destination Directory"
139 * editor="xdoclet.beans.FilePropertyEditor"
140 * @param destinationDir the directory where the plugin will write its files.
141 */
142 public void setDestinationDir(File destinationDir) {
143 _destinationDir = destinationDir;
144 _destinationDir.mkdirs();
145 }
146
147 /***
148 * Sets the destination directory. The reason why the argument is not a
149 * java.io.File is that
150 * the ant wrapper uses reflection in order to call this method, and it only works
151 * with java.lang.String. If this method is called explicitly (typically from
152 * an IDE wrapper), make sure the destination is an absolute path.
153 *
154 * @param destination the directory where the plugin will write its files.
155 */
156 public void setDestination(String destination) {
157 setDestinationDir( new File(destination) );
158 }
159
160 /***
161 * Sets the file name of the generated file(s). The value should be a plain
162 * a pattern such as Foo{0}.java or a plain String such as Bar.txt
163 * The occurrance of {0} will be substituted by the name of the class
164 * the file is generated from.
165 *
166 * @bean.property
167 * shortDescription="Name of generated file. If multiple files should be generated, use {0} in the name."
168 * displayName="Generated File"
169 * @param fileName the name of the generated file(s).
170 */
171 public void setFileName(String fileName) {
172 _fileName = fileName;
173 }
174
175 public final String getFileName() {
176 return _fileName;
177 }
178
179 /***
180 * Creates a new Accept
181 *
182 * @bean.method shortDescription="Add a new filter"
183 * displayName="New Filter"
184 */
185 public final Accept createAccept() {
186 if (getAccept() != null) {
187 throw new IllegalStateException("Only one accept is allowed");
188 }
189
190 Accept accept = new Accept();
191
192 add(accept);
193
194 return accept;
195 }
196
197 private Accept getAccept() {
198 return (Accept) CollectionUtils.find(this,
199 new Predicate() {
200 public boolean evaluate(Object o) {
201 return o instanceof Accept;
202 }
203 });
204 }
205
206 /***
207 * Creates a new PackageSubstitution
208 *
209 * @bean.method shortDescription="Add a new Package Substitution"
210 * displayName="New Package Substitution"
211 */
212 public final PackageSubstitution createPackageSubstitution() {
213 LogFactory.getLog(Plugin.class).info(this + ".createPackageSubstitution");
214
215 PackageSubstitution packageSubstitution = new PackageSubstitution()/package-summary.html">PackageSubstitution packageSubstitution = new PackageSubstitution();
216
217 add(packageSubstitution);
218
219 return packageSubstitution;
220 }
221
222 private Collection getPackageSubstitutions() {
223 return CollectionUtils.select(this,
224 new Predicate() {
225 public boolean evaluate(Object o) {
226 return o instanceof PackageSubstitution;
227 }
228 });
229 }
230
231 /***
232 * Generates the content.
233 *
234 * @throws XDocletException
235 */
236 public void execute() throws IOException, XDocletException {
237 validate();
238
239 File destinationFile;
240
241 if (getFileName() == null) {
242 throw new XDocletException("fileName was not specified for plugin " + getName());
243 }
244 if (getDestinationDir() == null) {
245 throw new XDocletException("destinationDir was not specified for plugin " + getName());
246 }
247
248 if (isGenerateOneFile()) {
249 Collection metaData = getFilteredMetadataCollection();
250 destinationFile = getDestinationFileForAll();
251 LogFactory.getLog(Plugin.class).info("Generating " + destinationFile.getAbsolutePath());
252 generate(destinationFile,metaData);
253 } else {
254 LogFactory.getLog(Plugin.class).info("Generating " + getFileName() + "...");
255
256 for (Iterator i = getFilteredMetadataCollection().iterator(); i.hasNext();) {
257 Object object = i.next();
258
259 destinationFile = getDestinationFileForOne(object);
260 LogFactory.getLog(Plugin.class).info("Generating " + destinationFile.getAbsolutePath());
261
262 generate(destinationFile,object);
263 }
264 }
265 }
266
267 /***
268 * Generates a file.
269 *
270 * @param destinationFile file to be generated.
271 * @param metaData metadata used during generation.
272 * @throws XDocletException if generation fails.
273 * @throws IOException if an IO error occurs.
274 */
275 protected abstract void generate(File destinationFile, Collection metaData)
276 throws IOException, XDocletException;
277
278 /***
279 * Generates a file.
280 *
281 * @param destinationFile file to be generated.
282 * @param metaData metadata used during generation.
283 * @throws XDocletException if generation fails.
284 * @throws IOException if an IO error occurs.
285 */
286 protected abstract void generate(File destinationFile, Object metaData)
287 throws IOException, XDocletException;
288
289 protected void validate()
290 throws XDocletException {
291 if ( !"".equals( _packageName )) {
292 if (!getPackageSubstitutions().isEmpty()) {
293 throw new XDocletException("Can't specify both packageSubstitution and packageName.");
294 }
295 }
296 }
297
298 /***
299 * Indicates whether or not to generate one file per metadata object or
300 * one file for all metadata objects.
301 *
302 * @return <code>true</code> if the fileName does not contain <code>"{0}"</code>.
303 * @see #setFileName(java.lang.String)
304 */
305 protected boolean isGenerateOneFile() {
306 return getFileName().indexOf("{0}") == -1;
307 }
308
309 /***
310 * Sets the package name of the generated files. This will be converted
311 * to a path and appended to destinationDir. Only specify this if no
312 * packageSubstitution is used.
313 *
314 * @bean.property
315 * shortDescription="Package name of generated file(s)."
316 * displayName="Package Name"
317 * @param packageName the package name.
318 */
319 public final void setPackageName(String packageName) {
320 _packageName = packageName;
321 }
322
323 public final String getPackageName() {
324 return _packageName;
325 }
326
327 private final String getSubstitutedPackageName(Object object)
328 throws XDocletException {
329 String packageName = null;
330 String originalPackageName = getMetadataProvider().getPackageName(object);
331
332 for (Iterator i = getPackageSubstitutions().iterator(); i.hasNext();) {
333 PackageSubstitution packageSubstitution = (PackageSubstitution) i/next()/package-summary.html">PackageSubstitution packageSubstitution = (PackageSubstitution) i.next();
334
335 // Check that we don't have a conflict. We must verify that there is not more than one match for from.
336 String candidate = packageSubstitution.getSubstitutedPackageName(originalPackageName);
337
338 if ((packageName != null) && (candidate != null)) {
339 throw new XDocletException("Ambiguous package substitution for "
340 + getMetadataProvider().getPackageName(object) + ". Both " + packageName + " or "
341 + candidate + " are possible substitution results.");
342 }
343
344 packageName = candidate;
345 }
346
347 <b>if (packageName == null) {
348 // If packageName is null (no match), use the originalPackageName
349 packageName = originalPackageName;
350 }
351
352 return packageName;
353 }
354
355 /***
356 * Gets all accepted objects. Subclasses can control what's accepted
357 * by calling {@link #createAccept} and call setPredicate on it, using
358 * a predicate from the xdoclet.util.predicates package. If no predicate
359 * is set, all classes that were parsed will be returned.
360 *
361 * @return all accepted classes.
362 */
363 protected final Collection getFilteredMetadataCollection()
364 throws XDocletException {
365
366 // TODO should we cache this? Probably.
367 Collection metadataCollection = getMetadataProvider().createMetadataCollection();
368
369 Collection result;
370
371 if (getAccept() != null) {
372 // Filter out the objects we want.
373 result = CollectionUtils.select(metadataCollection, getAccept());
374 } else {
375 result = metadataCollection;
376 }
377
378 if (result.isEmpty()) {
379 LogFactory.getLog(Plugin.class).warn("No objects for " + getName());
380 }
381
382 return result;
383 }
384
385 /***
386 * Creates the destination directory.
387 *
388 * @return the destination directory.
389 */
390 private final File getAndCreateRealDestDir(String packageName)
391 throws XDocletException {
392 >if( packageName == null ) {
393 // if package name is null, use the global one.
394 packageName = getPackageName();
395 }
396 >if( packageName == null ) {
397 throw new XDocletException( "packageName can't be null" );
398 }
399 if (getDestinationDir() == null) {
400 throw new XDocletException("destination was not specified for plugin \"" + getName() + "\"");
401 }
402
403 String packageNameAsPath = packageName.replace('.', File.separatorChar);
404 File dir = new File(getDestinationDir(), packageNameAsPath);
405
406 dir.mkdirs();
407
408 return dir;
409 }
410
411 /***
412 * Returns the destination file derived from a particular object. Will be
413 * called if fileName does not have "{0}" in it.
414 *
415 * @return the File where content will be written
416 * @throws XDocletException
417 */
418 public final File getDestinationFileForAll()
419 throws XDocletException {
420 return new File(getAndCreateRealDestDir(getPackageName()), getFileName());
421 }
422
423 /***
424 * Returns the destination file derived from a particular object. Will be
425 * called if fileName has "{0}" in it.
426 *
427 * @param object the object the generated file is derived from
428 * @return the File where content will be written
429 * @throws XDocletException
430 */
431 public final File getDestinationFileForOne(Object object)
432 throws XDocletException {
433 String fileNameSubstitutionValue = getMetadataProvider().getFilenameSubstitutionValue(object);
434 String packageName = getSubstitutedPackageName(object);
435
436 File destinationDir = null;
437 String[] formatArguments = null;
438
439 if (getFileName().indexOf("{1}") != -1) {
440 formatArguments = new String[] { fileNameSubstitutionValue, packageName };
441
442 // Don't use package name when finding the destDir. It's in the fileName
443 packageName = "";
444 } else {
445 formatArguments = new String[] { fileNameSubstitutionValue };
446 }
447
448 destinationDir = getAndCreateRealDestDir(packageName);
449
450 String fileName = MessageFormat.format(getFileName(), formatArguments);
451
452 return new File(destinationDir, fileName);
453 }
454
455 /***
456 * Throws XDocletException if a specific class is not on the CP. Should be called from subclasses
457 * constructors to verify that classpath is OK.
458 *
459 * @param className the name of the class to check.
460 */
461 protected static void checkClass(String className)
462 throws XDocletException {
463 try {
464 Class.forName(className);
465 } catch (ClassNotFoundException e) {
466 throw new XDocletException("Couldn't load " + className
467 + ". Make sure you have this class on the classpath used to define XDoclet.");
468 }
469 }
470
471 /***
472 * Hook to JXPath, which lets templates query the datamodel with xpath.
473 *
474 * @param contextBean the bean to query.
475 * @param xpath the xpath expression.
476 * @return the resulting bean.
477 */
478 public Object jxpath(Object contextBean, String xpath) {
479 Object result = null;
480 try {
481 // We're using reflection here to avoid dependency on jxpath.
482 // JXPathContext context = JXPathContext.newContext(contextBean);
483 // result = context.getValue(xpath);
484
485 Class jxPathContextClass = getClass().getClassLoader().loadClass("org.apache.commons.jxpath.JXPathContext");
486 Method newContextMethod = jxPathContextClass.getMethod( "newContext", new Class[]{ Object.class } );
487 Object jxPathContext = newContextMethod.invoke( null, new Object[] { contextBean } );
488 Method getValueMethod = jxPathContext.getClass().getMethod( "getValue", new Class[]{ String.class } );
489 result = getValueMethod.invoke( jxPathContext, new Object[] { xpath } );
490
491 LogFactory.getLog(Plugin.class).debug( contextBean + "," + xpath + "->" + result );
492 } catch( ClassNotFoundException e ) {
493
494 } catch( NoSuchMethodException e ) {
495
496 } catch( SecurityException e ) {
497
498 } catch( IllegalAccessException e ) {
499
500 } catch( IllegalArgumentException e ) {
501
502 } catch( InvocationTargetException e ) {
503 e.printStackTrace();
504 } catch( LogConfigurationException e ) {
505
506 }
507 return result;
508 }
509
510 // The default implementation of MetadataProvider will just delegate
511 // to the MetadataProvider hooked onto the XDoclet object.
512
513 public final Collection AcreateMetadataCollection() throws XDocletException {
514 // return getXDocletMetadataProvider().createMetadataCollection();
515 return null;
516 }
517
518 public final String AgetFilenameSubstitutionValue(Object o) throws XDocletException {
519 // return getXDocletMetadataProvider().getFilenameSubstitutionValue(o);
520 return null;
521 }
522
523 public final String AgetPackageName(Object o) {
524 // return getXDocletMetadataProvider().getPackageName(o);
525 return null;
526 }
527
528 public final void Acleanup() throws XDocletException {
529 // getXDocletMetadataProvider().cleanup();
530 }
531
532 public final void AsetClasspath( String classpath ) throws XDocletException {
533 // getXDocletMetadataProvider().setClasspath( classpath );
534 }
535 }
This page was automatically generated by Maven